﻿using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Reflection;
using System.Windows.Data;
using System.Diagnostics.Contracts;
using System.Data;

namespace ReactiveCode.Observables
{
    using Util;
    using Observables;

    partial class ChangeListening
    {
        static ChangeListening()
        {
            _loadingStandard = true;
            try
            {
                RegisterListener(NotifyPropertyChangedListener);
                RegisterListener(NamedEventListener);
                RegisterListener(DependencyPropertyListener);
                RegisterListener(TimeListener);
                RegisterListener(VariableListener);
            }
            finally
            {
                _loadingStandard = false;
            }
        }

        #region Named events
        static MemberListener NamedEventListener(PropertyInfo property)
        {
            if (property.DeclaringType != null && !property.IsIndexer())
            {
                var changedEvent = property.DeclaringType.GetEvent(property.Name + "Changed");
                if (changedEvent != null && (changedEvent.EventHandlerType == typeof(EventHandler) || changedEvent.EventHandlerType == typeof(EventHandler<EventArgs>)))
                    return (obj, args) => from _ in Observable.FromEvent<EventArgs>(obj, changedEvent.Name)
                                          select property.GetValue(obj, args);
            }

            return null;
        }
        #endregion

        #region INotifyPropertyChanged
        static MemberListener NotifyPropertyChangedListener(PropertyInfo property)
        {
            if (property.DeclaringType == null || !typeof(INotifyPropertyChanged).IsAssignableFrom(property.DeclaringType) || property.IsIndexer())
                return null;

            var getter = ReflectionUtil.ToGetter(property);
            return (obj, args) => from e in Observable.FromEvent<PropertyChangedEventArgs>(obj, "PropertyChanged").Select(x => x.EventArgs)
                                  where e == null || string.IsNullOrEmpty(e.PropertyName) || e.PropertyName == property.Name
                                  select getter(obj, args);
        }
        #endregion

        #region Dependency properties
        internal class DependencyPropertyListenerMediator : DependencyObject, IDisposable
        {
            IObserver<object> _observer;
            DependencyObject _instance;
            DependencyProperty _property;

            public DependencyPropertyListenerMediator(DependencyObject instance, DependencyProperty property, IObserver<object> observer)
            {
                Contract.Requires(instance != null && property != null && observer != null);
                _observer = observer;
                _instance = instance;
                _property = property;
                bool isAttached = instance.GetType().GetProperty(property.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) == null;
                string path = isAttached ? string.Format("({0}.{1})", property.OwnerType.Name, property.Name) : property.Name;
                BindingOperations.SetBinding(this, ValueProperty, new Binding { Source = instance, Path = new PropertyPath(path) });
                observer.OnNext(instance.GetValue(property));
            }
            public void Dispose()
            {
                _observer = null;
                Action clear = delegate
                {
                    BindingOperations.ClearBinding(this, ValueProperty);
                };
                if (Dispatcher.Thread == System.Threading.Thread.CurrentThread)
                    clear();
                else
                    Dispatcher.BeginInvoke(clear);
            }

            public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(DependencyPropertyListenerMediator),
                new PropertyMetadata(OnChanged));

            static void OnChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
            {
                var mediator = (DependencyPropertyListenerMediator)obj;
                if (mediator._observer != null)
                    mediator._observer.OnNext(e.NewValue);
            }
        }
        static MemberListener DependencyPropertyListener(MemberInfo member)
        {
            if (member == null || member.DeclaringType == null)
                return null;

            string propName = null;
            bool isAttached = false;
            var property = member as PropertyInfo;
            var method = member as MethodInfo;
            if (property != null && typeof(DependencyObject).IsAssignableFrom(property.DeclaringType))
            {
                propName = property.Name;
            }
            else if (method != null && method.IsStatic && method.Name.StartsWith("Get"))
            {
                var args = method.GetParameters();
                if (args.Length == 1 && typeof(DependencyObject).IsAssignableFrom(args[0].ParameterType))
                {
                    isAttached = true;
                    propName = method.Name.Substring(3);
                }
            }

            if (propName != null)
            {
                var depPropField = member.DeclaringType.GetField(propName + "Property", BindingFlags.Public | BindingFlags.Static);
                if (depPropField != null && typeof(DependencyProperty).IsAssignableFrom(depPropField.FieldType))
                {
                    var depProp = (DependencyProperty)depPropField.GetValue(null);
                    return (obj, args) => 
                    {
                        var depObj = isAttached ? args.Single() : obj;
                        return Observable.CreateWithDisposable<object>(o => new DependencyPropertyListenerMediator((DependencyObject)depObj, depProp, o));
                    };
                }
            }
            return null;
        }
        #endregion

        #region Time

        static TimeSpan _defaultTimerInterval = TimeSpan.FromSeconds(1);
        public static TimeSpan DefaultTimerInterval
        {
            get { return _defaultTimerInterval; }
            set
            {
                Contract.Requires(value > TimeSpan.Zero);
                _defaultTimerInterval = value;
            }
        }
        static MemberListener TimeListener(PropertyInfo property)
        {
            if (property.DeclaringType == typeof(DateTime))
            {
                if (property.Name == "Now")
                    return (obj, args) => Observable.Timer(TimeSpan.Zero, _defaultTimerInterval).Select(_ => (object)DateTime.Now);
                else if (property.Name == "Today")
                    return (obj, args) => Observable.Timer(TimeSpan.Zero, TimeSpan.FromDays(1)).Select(_ => (object)DateTime.Today);
            }
            else if (property.DeclaringType == typeof(Environment) && property.Name == "TickCount")
                return (obj, args) => Observable.Timer(TimeSpan.FromTicks(1)).Select(_ => (object)Environment.TickCount);

            return null;
        }
        #endregion

        #region Variable
        static MemberListener VariableListener(PropertyInfo property)
        {
            if (property.DeclaringType != null && typeof(IVariable).IsAssignableFrom(property.DeclaringType))
                return (var, _) => (var is IVariable) ? (var as IVariable).Observe() : Observable.Return<object>(null);
            return null;
        }
        #endregion
    }
}
